!function (e,w){
	function Particle(option) {
		this.originOption = {
			el: "#myCanvas",// canvas 选择器 ( 可配置 )
			f: 1200, // 焦距 ( 可配置 )
			dis: 14000, // 视距 ( 可配置 )
			rY: 0, // 初始 y 轴旋转角度 ( 可配置 )
			rX: 0, // 初始 x 轴旋转角度 ( 可配置 )
			mode: 5, // 模式 ( 可配置 )
			preEmit: false, // 是否预运行 ( 可配置 )
			draggable: true, // 是否允许拖拽 ( 可配置 )
			pList: [], // 集中管理粒子 ( 可配置 )
			lightList: [], // 灯光队列
			mainWidth: w.innerWidth, // 画布宽
			mainHeight: w.innerHeight, // 画布高
			canvas: null, // canvas dom
			cx: null, // canvas2d 上下文对象
			emitterPos: [400, 400], // 发射器坐标
			nPs: 20, // 每秒发射的粒子数量
			timer: null, // 动画计时器
			cTimer: null, // 创建粒子计时器
			life: 5000, // 粒子生命 (ms)
			ms: 0, // 当前时间
			dur: 10, // 发射粒子间隔(需初始化)
			isPress: false, // 鼠标是否按下
			dragX: 0, // x 方向拖拽量
			dragY: 0, // y 方向拖拽量
			_currX: 0, // x 方向临时拖拽量
			_currY: 0 ,// y 方向临时拖拽量
			swing: 50, // 全局振幅
			freq: 1, // 全局振频
			userDis: false, // 用户是否设置视距
			userN: false, // 用户是否设置横纵数量
			zoomQuotiety: 30, // 滚轮缩放速度
			drawFace: true, // 是否绘制面(tCubic模式)
			drawSide: false, // 是否绘制线框(tCubic模式)
			drawAnchor: false ,// 是否绘制顶点(tCubic模式)
			glow: false,
			showTail: false, // 是否开启拖尾模式
		}
		Object.assign(this, this.originOption, option)
		;option.dis && (this.userDis = true)
		;(option.nX || option.nY) && (this.userN = true)
		this.init()
	}
	Particle.prototype.expend = function(obj) {Object.assign(this, obj)}
	Particle.prototype.expend({
		init() {
			this.dur = Math.floor(1000 / this.nPs) // 初始化粒子发射时间间隔
			this.dragY = this.rX * 180 / Math.PI
			this.dragX = this.rY * 180 / Math.PI
			this.canvas = e.querySelector(this.el)
			if(!this.canvas){
				throw Error("Please provide an enable canvas element selector for option 'el'")
				return false
			}
			if(this.canvas.isUsed){
				throw Error("Reduplicate canvas element, please provide another enable canvas element selector for option 'el', or use function 'destroy()' to remove this instance first!")
				return false
			}
			this.cx = this.canvas.getContext("2d")
			this.canvas.setAttribute("width",this.mainWidth)
			this.canvas.setAttribute("height",this.mainHeight)
			this.showTail && (this.canvas.style.mixBlendMode = "screen")
			this.canvas.isUsed = true // 占用
			this.emitterPos = [this.mainWidth / 2, this.mainHeight / 2]
			this.addEvent()
			this.emit()
		},
		mouse: [0,0],
		addEvent() {
			this.windowMousedown = () => {
				if(this.draggable){
					this.isPress = true
					this._currX = event.pageX + this.dragX
					this._currY = event.pageY / 2 + this.dragY
				}
			}
			this.windowMousemove = () => {
				if (this.isPress && this.draggable) {
					this.dragX = this._currX - event.pageX
					this.dragY = this._currY - event.pageY / 2
					this.rY = this.dragX * Math.PI / 180
					this.rX = this.dragY * Math.PI / 180
				}
				this.mouse[0] = event.pageX
				this.mouse[1] = event.pageY
			}
			this.windowMouseup = () => {
				if(this.draggable){
					this.isPress = false
				}
			}
			this.windowMousewheel = event => {
				if(this.draggable){
					if(event.deltaY){
						this.dis += event.deltaY * this.zoomQuotiety
					}else{ // 兼容火狐
						this.dis += event.detail * 33 * this.zoomQuotiety
					}
				}
			}
			this.windowResize = () => {
				this.mainWidth = w.innerWidth
				this.mainHeight = w.innerHeight
				this.canvas.setAttribute("width",this.mainWidth)
				this.canvas.setAttribute("height",this.mainHeight)
			}
			w.addEventListener("mousedown",this.windowMousedown)
			w.addEventListener("mousemove",this.windowMousemove)
			w.addEventListener("mouseup",this.windowMouseup)
			w.addEventListener("mousewheel",this.windowMousewheel)
			w.addEventListener("DOMMouseScroll",this.windowMousewheel)
			w.addEventListener("resize",this.windowResize)
		},
		destroy(){
			// 解绑所有事件
			w.removeEventListener("mousedown",this.windowMousedown)
			w.removeEventListener("mousemove",this.windowMousemove)
			w.removeEventListener("mouseup",this.windowMouseup)
			w.removeEventListener("mousewheel",this.windowMousewheel)
			w.removeEventListener("DOMMouseScroll",this.windowMousewheel)
			w.removeEventListener("resize",this.windowResize)
			// 清除计时器
			clearInterval(this.timer)
			clearInterval(this.cTimer)
			this.cx.clearRect(0,0,this.mainWidth,this.mainHeight)
			// 解除 canvas dom 占用
			this.canvas.isUsed = false
		},
		emit() {
			this["mode"+this.mode]()
		},
		mode1(){
			this.f = 800 // 焦距
			this.dis = 1600 // 视距
			this.zoomQuotiety = 1
			// this.dragY = this.rX * 180 / Math.PI
			this.showTail = true
			;(e.body || e.documentElement).style.backgroundImage = "linear-gradient(#0b1330 0,#364578 100%)"
			// 创建粒子
			for (var i = 0; i < 10; i++) {
				for (var j = 0; j < 10; j++) {
					this.pList.push({
						pos: [(i - 5) * 120, (j - 5) * 80, (Math.random() - .5) * 1200], // 粒子三维坐标
						size: Math.random() * 10 + 2, // 粒子尺寸
						color: "hsl("+Math.floor(Math.random()*360)+",100%,"+(Math.floor(Math.random()*15)+85)+"%)", // 粒子颜色
						age: 0 // 粒子年龄
					})
				}
			}
			// 绘制
			this.draw(()=>{
				this.pList.forEach(p => {
					if(!this.isPress){
						this.rY = this.ms / 1000
						this.rX = -0.3
					}
					this.cx.save()
					var p2d = this.deDiv(p.pos)
					if(p2d.mul >= 0){
						this.cx.fillStyle = p.color
						this.cx.shadowBlur = 30
						this.cx.shadowColor = p.color
						this.cx.beginPath()
						this.cx.arc(...p2d.res, p.size * p2d.mul,0,Math.PI*2)
						this.cx.fill()
					}
					this.cx.restore()
				})
			})
		},
		mode2(){
			this.dis = 3000
			this.rX = -0.05
			this.dragY = this.rX * 180 / Math.PI
			this.zoomQuotiety = 2
			;(e.body || e.documentElement).style.backgroundImage = ""
			// 创建粒子
			for (var i = -20; i < 20; i++) {
				for (var j = -50; j < 50; j++) {
					this.pList.push({
						pos: [i*40,0,j*40], // 粒子三维坐标
						phaseX: Math.PI * (i) / 10, // 粒子固有相位
						phaseY: Math.PI * (j) / 10,// 粒子固有相位
						size: 4
					})
				}
			}
			// 绘制
			this.cx.fillStyle = "rgba(0,255,255,.8)"
			this.draw(()=>{
				// 计算
				this.pList.forEach(p=>{
					var dX = Math.sin(p.phaseX + this.ms / 500 * this.freq)
					var dY = Math.sin(p.phaseY + this.ms / 500 * this.freq)
					var d = dX + dY
					p.pos[1] = d * this.swing
					p.size = 6 - d * 2
				})
				this.pList.forEach(p => {
					var p2d = this.deDiv(p.pos)
					if(p2d.mul >= 0){
						this.cx.fillStyle = "rgba(0,255,255,"+(1-p.size/10)+")"
						this.cx.beginPath()
						this.cx.arc(...p2d.res,p.size*p2d.mul,0,Math.PI*2)
						this.cx.fill()
					}
				})
			})
		},
		mode3(){
			this.dis = this.userDis ? this.dis : 14000
			this.rX = 1
			this.dragY = this.rX * 180 / Math.PI
			;(e.body || e.documentElement).style.backgroundImage = "linear-gradient(to top,#ffeabe 0,#5d2f2f 100%)"
			// 创建粒子
			var count = 0
			var nX = this.userN ? this.nX : 20
			var nY = this.userN ? this.nY : 16
			var pWidth = 400 // 六边形边长
			var dis = 50 // 六边形间距
			var g3 = Math.pow(3,0.5) // 提前计算根号 3 的值
			var sixElement = [ // 六边形基元各个顶点坐标
				[0, 0],
				[pWidth, 0],
				[pWidth * 1.5, pWidth / 2 * g3],
				[pWidth, pWidth * g3],
				[0, pWidth * g3],
				[-pWidth / 2, pWidth / 2 * g3]
			]
			var oOffsetX = (pWidth * 3 + dis * g3) // 双六边形基元 X 偏移
			var oOffsetY = (pWidth * g3 + dis) // 双六边形基元 Y 偏移
			function off(sixEl,oX,oY){
				var arr = []
				sixEl.forEach(axis=>{
					arr.push([axis[0] + oX, axis[1] + oY])
				})
				return arr
			}
			var sixElement2 = off(sixElement,oOffsetX/2,oOffsetY/2) // 初次右下偏移的新六边形
			sixElement = [[...sixElement],[...sixElement2]] // 获得双六边形基元坐标集 (坐标x12)
			var realNX = Math.round(nX / 2)
			for(var i = 0; i < nY; i++){
				for(var j = 0; j < realNX; j++){
					var posArr = []
					sixElement.forEach(sixEl=>{
						var arr = []
						sixEl.forEach(p=>{
							var x = p[0] + (j-realNX/2) * oOffsetX
							var y = p[1] + (i-nY/2) * oOffsetY
							// arr.push([x, y, Math.pow(x*y,2) / 200000]) // 迪拉修姆光流
							arr.push([x, y, Math.pow(x,2) / 18000])
						})
						posArr.push(arr)
					})
					posArr.forEach(pos=>{ // 创建两次, 原位 和 右下偏移位
						// *********************  创建单个六边形对象
						this.pList.push({
								pos,
								color: [255,255,255],
								opacity: 1,
								// count: ++count
								count: Math.floor(Math.random() * 1000)
						})
					})
				}
			}
			// 绘制
			this.draw(()=>{
				this.pList.forEach(p => {
					// 透明度动画
					p.opacity = Math.abs(Math.sin(this.ms / 2000 + p.count)) *.8  - .2
					p.opacity = p.opacity < .2 ? .2 : p.opacity
					this.cx.fillStyle = "rgba("+p.color+","+p.opacity+")"
					this.cx.beginPath()
					var visable = true
					p.pos.forEach((sixEl,index)=>{
						// 开始绘制立体六边形
						var p2d = this.deDiv(sixEl)
						if(p2d.mul < 0){
							visable = false
							return false
						}
						if(index === 0){
							this.cx.moveTo(...p2d.res)
						}else{
							this.cx.lineTo(...p2d.res)
						}
					})
					this.cx.closePath()
					visable && this.cx.fill()
					var temp = Math.abs(Math.sin(this.ms / 1000 + p.count + Math.PI))
					this.cx.strokeStyle = "rgba("+p.color+","+ temp +")"
					this.cx.stroke()
				})
			})
		},
		mode4(){
			this.dis = this.userDis ? this.dis : 38000
			this.rX = 0.55
			this.rY = -0.55
			this.dragY = this.rX * 180 / Math.PI
			this.dragX = this.rY * 180 / Math.PI
			;(e.body || e.documentElement).style.backgroundImage = "linear-gradient(to top,#ffeabe 0,#5d2f2f 100%)"
			// 创建粒子
			var nX = this.userN ? this.nX : 32
			var nY = this.userN ? this.nY : 20
			var count = 0
			var pWidth = 1000 // 六边形边长
			var dis = -700 // 六边形间距
			var g3 = Math.pow(3,0.5) // 提前计算根号 3 的值
			var sixElement = [ // 六边形基元各个顶点坐标
				[0, 0],
				[pWidth, 0],
				[pWidth * 1.5, pWidth / 2 * g3],
				[pWidth, pWidth * g3],
				[0, pWidth * g3],
				[-pWidth / 2, pWidth / 2 * g3]
			]
			var oOffsetX = (pWidth * 3 + dis * g3) // 双六边形基元 X 偏移
			var oOffsetY = (pWidth * g3 + dis) // 双六边形基元 Y 偏移
			function off(sixEl,oX,oY){
				var arr = []
				sixEl.forEach(axis=>{
					arr.push([axis[0] + oX, axis[1] + oY])
				})
				return arr
			}
			var sixElement2 = off(sixElement,oOffsetX/2,oOffsetY/2) // 初次右下偏移的新六边形
			sixElement = [[...sixElement],[...sixElement2]] // 获得双六边形基元坐标集 (坐标x12)
			var realNX = Math.round(nX / 2)
			for(var i = 0; i < nY; i++){
				for(var j = 0; j < realNX; j++){
					var posArr = []
					sixElement.forEach(sixEl=>{
						var arr = []
						var zIndex = (Math.random() - .5 ) * 1000
						var scale = Math.random() * 2 + .3
						sixEl.forEach(p=>{
							var x = p[0] * scale + (j-realNX/2) * oOffsetX
							var y = p[1] * scale + (i-nY/2) * oOffsetY
							// arr.push([x, y, Math.pow(x,2) / 18000]) // 弧面
							arr.push([x, y, zIndex])
						})
						posArr.push(arr)
					})
					posArr.forEach(pos=>{ // 创建两次, 原位 和 右下偏移位
						// *********************  创建单个六边形对象
						this.pList.push({
								pos,
								color: [255,255,255],
								opacity: 1,
								// count: ++count,
								count: Math.floor(Math.random() * 1000),
								sparcle: Math.floor(Math.random() * 1.2),
								speed: Math.random() + 1
						})
					})
				}
			}
			// 绘制
			this.cx.lineWidth = 1
			this.draw(()=>{
				// 计算
				this.pList.forEach(box=>{
					var zIndex = Math.sin(this.ms / 10000 * box.speed + box.count)  * 10000 // 纵深动画距离
					box.pos.forEach(p=>{
						p[2] = zIndex
					})
				})
				this.pList.forEach(p => {
					// 透明度动画
					if(!p.sparcle){
						p.opacity = Math.abs(Math.sin(this.ms / 1000 + p.count)) *.8  - .6
					}else{
						p.opacity = Math.abs(Math.sin(this.ms / 10 + p.count)) * .4
					}
					p.opacity = p.opacity < 0 ? 0 : p.opacity
					this.cx.fillStyle = "rgba("+p.color+","+p.opacity+")"
					this.cx.beginPath()
					var visable = true
					p.pos.forEach((sixEl,index)=>{
						// 开始绘制立体六边形
						var p2d = this.deDiv(sixEl)
						if(p2d.mul < 0){
							visable = false
							return false
						}
						if(index === 0){
							this.cx.moveTo(...p2d.res)
						}else{
							this.cx.lineTo(...p2d.res)
						}
					})
					this.cx.closePath()
					visable && this.cx.fill()
					var temp = Math.abs(Math.sin(this.ms / 1000 + p.count + Math.PI))
					this.cx.strokeStyle = "rgba("+p.color+","+ temp +")"
					this.cx.stroke()
				})
			})
		},
		mode5(){
			this.dis = 20000
			this.rX = -0.56
			this.dragY = this.rX * 180 / Math.PI
			;(e.body || e.documentElement).style.backgroundImage = "linear-gradient(#0b1330 0,#364578 100%)"
			this.showTail = true
			this.canvas.style.mixBlendMode = "screen"
			var max_rY = 8 // 弧度，大约 20 圈
			var max_h = 3000 // 最大高度下降量
			var timeSize = 4 // 全局时间尺度
			// 创建粒子
			var createP = (age)=>{
				this.pList.push({
					rY: Math.PI * 2 * Math.random(),
					// r: 4000 * Math.random() + 4000, // 轴心距
					r: 10000 + Math.random() * 1000, // 轴心距
					h: -3000, // 高度
					s: 120,
					color: "hsl("+Math.floor(Math.random()*360)+",100%,"+(Math.floor(Math.random()*15)+85)+"%)",
					age: age ? age : 0
				})
				if(this.pList.length > 440) this.pList.shift()
			}
			if(this.preEmit){
				for(var i=0;i<100;i++){
					createP(i*200)
				}
			}
			this.cTimer = setInterval(createP,20)
			this.lightOrigin = [0,11000,0]
			var frame = ()=>{
				this.cx.fillStyle = "#fff"
				this.pList.forEach(p=>{
					p.age += 16
					var t = p.age * 20 / timeSize
					var rY = (p.rY + Math.pow(t / 20000,2)) * max_rY // 角度动画
					// var rY = p.rY
					var r = (60 / (t / 5000 + 1)) * p.r / 60 - 600 // 轴心距动画
					// var r = ((t / 500) / (t / 5000 + 1)) * p.r - 600 // 轴心距动画
					var s = t / 1000 * Math.pow(.5,t / 2000) * p.s + 10 // 尺寸动画
					var h = p.h + (Math.pow(t / 2000,2)) * max_h / 100 // 高度动画
					var x = r * Math.cos(rY)
					var z = r * Math.sin(rY)
					var y = h
					var p2d = this.deDiv([x,y,z])
					if(p2d.mul >= 0){
						this.cx.fillStyle = p.color
						this.cx.shadowBlur = 10
						this.cx.shadowColor = p.color
						this.cx.beginPath()
						this.cx.arc(...p2d.res, s * p2d.mul,0,Math.PI*2)
						// this.cx.fillRect(...p2d.res, s * p2d.mul, s * p2d.mul)
						this.cx.fill()
					}
				})
				var p2d = this.deDiv(this.lightOrigin)
				if(p2d.mul >= 0){
					this.cx.beginPath()
					this.cx.arc(...p2d.res,1000* p2d.mul,0,Math.PI*2)
					this.cx.fillStyle = "#fff"
					this.cx.shadowBlur=20;
					this.cx.shadowColor="#fff";
					this.cx.fill()
				}
			}
			this.draw(frame)
		},
		mode6(){
			;(e.body || e.documentElement).style.background = "#000"
			this.dis = 3000
			this.zoomQuotiety = 1
			for(var i = 0; i < 50; i++){
				this.pList.push({
					anchor: this.star({
						x: 0,
						y: 0,
						shrink: .6,
						size: Math.floor(Math.random() * 25+5),
					}),
					x: Math.floor((Math.random()-.5) * this.mainWidth),
					y: Math.floor((Math.random()-.5) * this.mainHeight),
					z: (Math.random()-.5) * 1000,
					r: Math.random() * Math.PI * 2,
					phase: Math.PI * i / 10, // 固有相位
				})
			}
			this.draw(()=>{
				this.pList.forEach(p=>{
					var able = true
					var d = Math.sin(p.phase + this.ms / 500 * this.freq) * 40
					this.cx.beginPath()
					p.anchor.forEach((an,i)=>{
						var rp = this.rotateY(...an,0,p.r+this.ms/150)
						var p2d = this.deDiv([rp[0]+p.x+d,rp[1]+p.y+d,rp[2]+p.z+d])
						this.cx[i==0?'moveTo':'lineTo'](...p2d.res)
						if(p2d.mul<0){
							able = false
							return
						}
					})
					this.cx.closePath()
					this.cx.fillStyle = "#fff"
					this.cx.shadowBlur = 20
					this.cx.shadowColor = "#fff"
					able && this.cx.fill()
				})
			})
		},
		mode7(){
			var cubicOptionList = []
			// 穿梭效果
			// this.f = 200
			// for(let i = 0; i < 3; i++){
			// 	for(let j = 0; j < 3; j++){
			// 		for(let k = 0; k < 20; k++){
			// 			cubicOptionList.push({
			// 				x: (i-1)*1000 + (Math.random()-.5) * 800,
			// 				y: (j-1)*1000 + (Math.random()-.5) * 800,
			// 				z: (k-1)*1000 + (Math.random()-.5) * 300,
			// 				s: 100,
			// 				isTransform: true,
			// 				sx: 200,
			// 				sy: 200,
			// 				sz: Math.random() * 100 + 100,
			// 				faceColor: [Math.floor(Math.random()*360),40,85],
			// 			})
			// 		}
			// 	}
			// }
			for(let i = 0; i < 3; i++){
				for(let j = 0; j < 3; j++){
					for(let k = 0; k < 3; k++){
						cubicOptionList.push({
							x: (i-1)*1000,
							y: (j-1)*1000,
							z: (k-1)*1000,
							s: 480,
							faceColor: [Math.floor(Math.random()*360),40,85],
							sideColor: [Math.floor(Math.random()*360),40,85],
							anchorColor: [190,50,75],
							id: [i,j,k]
						})
					}
				}
			}
			// 传入配置列表, 返回绘制回调函数
			var cubicCallback = this.tCubic({
				cubicOptionList,
				// screenMode: false, // 待加参数
				// preventDrawFace: true
			})
			// *******************  自定义动画
			var animations = (cubicResourceList) => {
				cubicResourceList.forEach(cubic => {
					cubic.faces.data.forEach(f => {
						f.forEach(p => {
							var phase = cubic.id[0]+cubic.id[1]+cubic.id[2]
							p[1] += Math.sin(this.ms/100 + phase/2) * 16
						})
					})
					// cubic.sides.data.forEach(s => {
					// 	s.forEach(p => {
					// 		var phase = cubic.id[0]+cubic.id[1]+cubic.id[2]
					// 		p[1] += Math.sin(this.ms/100 + phase/2) * 16
					// 	})
					// })
					// cubic.anchors.data.forEach(p => {
					// 	var phase = cubic.id[0]+cubic.id[1]+cubic.id[2]
					// 	p[1] += Math.sin(this.ms/100 + phase/2) * 16
					// })
				})
			}
			this.draw(()=>{
				// this.dis = 14000 - this.ms / 2
				cubicCallback(animations)
			})
		},
		mode8(){
			this.dis = 80000
			var _this = this
			var options = []
			for(let i=0; i<20; i++){
				for(let j=-1; j<2; j+=2){
					options.push({
						isTransform: true,
						get sy(){
							return Math.sin(_this.ms / 50 + i) * 200 + 2000
						},
						get x(){
							return (i-10) * 2400
						},
						get rX(){
							return _this.ms / 1000 + i / 3
						},
						sx: 200,
						sz: 400,
						faceColor: [Math.floor(Math.random()*360),50,100],
						sideColor: [Math.floor(Math.random()*360),60,85],
						anchorColor: [Math.floor(Math.random()*360),60,85],
						anchorSize: 80,
						originOffset: [0,j,0],
					})
				}
			}
			var cFrame = this.tCubic(options)
			this.draw(()=>{
				cFrame()
			})
		},
		draw(frame,isStatic,isFromConner){
			var t = this.showTail ? 10 : 16
			if(isStatic){
				frame.call(this)
			}else{
				this.timer = setInterval(() => {
					this.ms = performance.now()
					this.cx.save()
					!this.showTail && this.cx.clearRect(0,0,this.mainWidth,this.mainHeight)
					this.showTail && (this.cx.fillStyle = "#00000011",this.cx.fillRect(0,0,this.mainWidth,this.mainHeight))
					if(!isFromConner){
						this.cx.translate(this.mainWidth / 2, this.mainHeight / 2)
					}
					frame()
					this.cx.restore()
				}, 16)
			}
		},
		deDiv(p3d) {
			var res = this.rotateX(...this.rotateY(...p3d, this.rY), this.rX)
			// var res = this.rotateY(...this.rotateX(...p3d, this.rX), this.rY)
			var mul = this.f / (this.dis - res[2] - this.f) // 计算透视缩放系数
			// var mul = .4-this.dis / 80000 // 平行透视
			return {
				res: [res[0] * mul, res[1] * mul],
				mul,
				origin: res
			}
		},
		rotateY(x, y, z, a) {
			// 绕 Y 轴旋转角度 a
			var x1 = x,
				z1 = z;
			x1 = x * Math.cos(a) - z * Math.sin(a)
			z1 = z * Math.cos(a) + x * Math.sin(a)
			return [x1, y, z1]
		},
		rotateX(x, y, z, a) {
			// 绕 X 轴旋转角度 a
			var y1 = y,
				z1 = z;
			y1 = y * Math.cos(a) - z * Math.sin(a)
			z1 = z * Math.cos(a) + y * Math.sin(a)
			return [x, y1, z1]
		},
		getNVectorByPoints(p1,p2,p3){
			// 计算由 p1,p2,p3 三点决定平面的 法向量
			var v1 = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]]
			var v2 = [p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2]]
			return this.getCross(v1,v2)
			// return [v1[1]*v2[2]-v2[1]*v1[2], v2[0]*v1[2]-v1[0]*v2[2], v1[0]*v2[1]-v2[0]*v1[1]]
		},
		getCross(v1,v2){// 计算 v1,v2 两个向量的叉积
			return [v1[1]*v2[2]-v2[1]*v1[2], v2[0]*v1[2]-v1[0]*v2[2], v1[0]*v2[1]-v2[0]*v1[1]]
		},
		getVectorCos(v1,v2){
			// 计算两向量夹角余弦值
			var a = v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]
			var b = Math.sqrt((Math.pow(v1[0],2)+Math.pow(v1[1],2)+Math.pow(v1[2],2)) * (Math.pow(v2[0],2)+Math.pow(v2[1],2)+Math.pow(v2[2],2)))
			return b===0 ? 0 : a / b < 0 ? 0 : a / b
		},
		// 索引循环
		iCir(tempI,length){
			while(tempI < 0){
				tempI += length
			}
			return tempI % length
		},
		anchorShader(mixCRes){
			mixCRes.anchors.forEach(p => {
				this.cx.fillStyle = "hsl("+p.color[0]+","+p.color[1]+"%,"+p.color[2]+"%)"
				var p2d = this.deDiv(p.data)
				;(p2d.mul>0) && this.cx.fillRect(p2d.res[0]-1,p2d.res[1]-p.size/2*p2d.mul,p.size*p2d.mul,p.size*p2d.mul)
			})
		},
		sideShader(mixCRes){
			mixCRes.sides.forEach(s => {
				if(s.color.length === 3){
					this.cx.strokeStyle = "hsl("+s.color[0]+","+s.color[1]+"%,"+s.color[2]+"%)"
				}else if(s.color.length === 4){
					this.cx.strokeStyle = "rgba("+s.color[0]+","+s.color[1]+","+s.color[2]+","+s.color[3]+")"
				}
				var able = true
				this.cx.beginPath()
				s.data.forEach((l,i) => {
					var p2d = this.deDiv(l)
					;(p2d.mul<0) && (able=false)
					this.cx[i===0?'moveTo':'lineTo'](...p2d.res)
					this.cx.lineWidth = p2d.mul*p2d.mul*s.lineWidth
				})
				this.cx.closePath()
				able && this.cx.stroke()
			})
		},
		faceShader(mixCRes){
			// 对 面数据 的处理和绘制
			// mixCRes.faces.sort((fa,fb)=>{ // 按面重心 z 位置升序, 依次绘制, 实现遮挡
			// 	var z = f => {
			// 		return f.data.reduce((pre,next)=>{
			// 			var p2d = this.deDiv(next)
			// 			return pre + p2d.origin[2]
			// 		},0) / f.data.length
			// 	}
			// 	return z(fa) - z(fb)
			// })
			// 先计算每个面的法向量, 保存到面数据上
			mixCRes.faces.forEach(f => {
				var p3 = [] // 取前 3 个点ABC, 计算 AB, AC 向量的叉积, 创建数据时已确保 AB, AC 均为凸边
				f.data.forEach((an,i) => {
					if(i < 3){
						p3.push(this.deDiv(an).origin)
					}else{
						return
					}
				})
				f.vx = this.getNVectorByPoints(...p3)
			})
			mixCRes.faces.sort((fa,fb)=>{ // 按面重心 z 位置 法向量叉积 综合排序
				var z = f => {
					return f.data.reduce((pre,next)=>{
						var p2d = this.deDiv(next)
						return pre + p2d.origin[2]
					},0) / f.data.length
				}
				var a = Boolean(z(fa) - z(fb) > 0)
				var b = Boolean(this.getCross(fa.vx,fb.vx) > 0)
				return (Number((a || b) && (!a || !b)) - .5) // 布尔异或转正负
			})
			mixCRes.faces.forEach(f => {
				// 计算着色
				var lightness = 0 // 初始化着色强度
				this.lightList.forEach(light=>{ // 计算总着色强度
					if(light.type == "env"){
						lightness += light.lightness
					}else if(light.type == "parallel"){
						lightness += this.getVectorCos(f.vx,light.vector) * light.lightness
					}
				})
				if(f.color.length === 3){
					this.cx.fillStyle = "hsl("+f.color[0]+","+f.color[1]+"%,"+f.color[2] * lightness/100+"%)"
				}else if(f.color.length === 4){
					this.cx.fillStyle = "rgba("+f.color[0]*lightness/100+","+f.color[1]*lightness/100+","+f.color[2]*lightness/100+","+f.color[3]+")"
					// this.cx.fillStyle = "rgba("+f.color[0]+","+f.color[1]+","+f.color[2]+","+f.color[3]+")"
				}
				// ************************** 发光效果
				if(this.glow){
					this.cx.shadowBlur = 20
					this.cx.shadowColor = "#ffffff88"
				}
				
				// ***************************************
				// 均色半透材质
				// this.cx.fillStyle = "rgba(200,255,255,"+lightness/100+")"
				// 玻璃材质
				// this.cx.fillStyle = "rgba("+160*lightness/100+","+220*lightness/100+","+255*lightness/100+",.3)"
				// 绘制面
				this.cx.beginPath()
				f.data.every((p, i) => {
					var p2d = this.deDiv(p)
					this.cx[i == 0 ? 'moveTo' : 'lineTo'](...p2d.res)
					return p2d.mul > 0
				}) && (this.cx.closePath(),this.cx.fill())
			})
		},
		// **********************  个性功能区
		star(option){
			// 输入中心坐标，尺寸，收缩量，是否星形，输出正多边形（星形）各个顶点依次的坐标
			var star = {
				x: 100,
				y: 100,
				isStar: true,	// 是否为星形
				n: 5,			// 边数
				shrink: .35,		// 收缩量
				size: 40,		// 长半径
				rotate: 0,
				// thickness: 40,
				// color: [180,50,80]
			}
			Object.assign(star,option)
			var anchors = []
			if(star.isStar){
				for(var i = 0; i < star.n * 2; i++){
					var step = Math.PI / star.n * i - Math.PI / 2 + star.rotate
					if(i % 2 == 0){
						anchors.push([
							Math.cos(step) * star.size + star.x,
							Math.sin(step) * star.size + star.y
						])
					}else{
						anchors.push([
							Math.cos(step) * star.size * star.shrink + star.x,
							Math.sin(step) * star.size * star.shrink + star.y
						])
					}
				}
			}else{
				for(var i = 0; i < star.n; i++){
					var step = Math.PI / star.n * i * 2 - Math.PI / 2 + star.rotate
					anchors.push([
						Math.cos(step) * star.size + star.x,
						Math.sin(step) * star.size + star.y
					])
				}
			}
			return anchors // 路径格式
		},
		tStar(options){
			// 新建柱体星星源数据队列
			var starResourceList = []
			// 新建整合数据对象
			var mixCRes = {}
			// 新建一个平行光源
			this.lightList.push({
				type: "parallel",
				vector: [-1,1,-2],
				lightness: 75
			})
			this.lightList.push({
				type: "parallel",
				vector: [2,1,0],
				lightness: 20
			})
			// 新建一个环境光
			this.lightList.push({
				type: "env",
				vector: [1,0,0],
				lightness: 10
			})
			var _s = option => {
				var s = {
					isStar: true,	// 是否为星形
					n: 5,			// 边数
					shrink: .35,		// 收缩量
					size: 400,		// 长半径
					thickness: 40, // 厚度
					faceColor: [180,50,80], // 面颜色
					anchorColor: [120,50,80], // 顶点颜色
					anchorSize: 10, // 顶点尺寸
					x: 0, // (不可配置)
					y: 0, // (不可配置)
					rotate: 0, // (不可配置)
				}
				option && Object.assign(s,option)
				var anchors = [] // 输出
				var dAnchors = [] // 临时
				var sides = []
				var faces = []
				var an = this.star(s)
				// 生成顶点数据
				an.forEach(p => {
					dAnchors.push([p[0],p[1],-s.thickness/2])
					dAnchors.push([p[0],p[1],s.thickness/2])
				})
				dAnchors.forEach(data => {
					anchors.push({
						color: s.anchorColor,
						data,
						size: s.anchorSize
					})
				})
				// 生成面数据
				var upFace = {
					color: s.faceColor,
					data: []
				}
				var bottomFace = {
					color: s.faceColor,
					data: []
				}
				var sideFaces = []
				var len = dAnchors.length
				dAnchors.forEach((p,i,aL) => {
					if(i%2===0){
						bottomFace.data[this.iCir(i/2-1,len/2)] = p
						// 生成侧面路径数据
						var data = [p,aL[this.iCir(i+1,len)],aL[this.iCir(i+3,len)],aL[this.iCir(i+2,len)]]
						sideFaces.push({color: s.faceColor,data})
					}else{
						upFace.data[this.iCir(len/2-(i-1)/2-1,len/2)] = p // 倒序以纠正法向量朝向
					}
				})
				faces.push(bottomFace)
				faces.push(upFace)
				faces = faces.concat(sideFaces)
				console.log(faces)
				console.log(dAnchors)
				return {
					anchors,
					sides,
					faces,
					id: s.id
				}
			}
			var _construct = () => {
				starResourceList = []
				if(options){
					if(options.length !== undefined){
						options.forEach(option=>{
							starResourceList.push( _s(option)) // 用户配置的立方体
						})
					}else if(options.cubicOptionList){
						if(options.cubicOptionList.length !== undefined){
							options.cubicOptionList.forEach(option=>{
								starResourceList.push( _s(option)) // 用户配置的立方体
							})
						}else{
							starResourceList.push( _s()) // 默认配置立方体
						}
					}else{
						starResourceList.push( _s()) // 默认配置立方体
					}
				}else{
					starResourceList.push( _s()) // 默认配置立方体
				}
			}
			 // 整合函数
			var _combine = () => {
				mixSRes ={
					anchors: [],
					sides: [],
					faces: []
				}
				starResourceList.forEach(sRes=>{
					sRes.anchors.forEach(a => {
						mixSRes.anchors.push(a)
					})
					// sRes.sides.data.forEach(data => {
					// 	mixSRes.sides.push({color: sRes.sides.color,data})
					// })
					sRes.faces.forEach(f => {
						mixSRes.faces.push(f)
					})
				})
			}
			_construct() // 构建
			_combine() // 整合
			return () => {
				_construct()
				_combine()
				this.faceShader(mixSRes)
			}
		},
		tCubic(options){
			// 新建方块源数据队列
			var cubicResourceList = []
			// 新建整合数据对象
			var mixCRes = {}
			// 新建一个平行光源
			this.lightList.push({
				type: "parallel",
				vector: [-1,1,-2],
				lightness: 75
			})
			this.lightList.push({
				type: "parallel",
				vector: [2,1,0],
				lightness: 20
			})
			// 新建一个环境光
			this.lightList.push({
				type: "env",
				vector: [1,0,0],
				lightness: 10
			})
			var _c = option => { // 计算出一个方块源数据
				var _this =  this
				const c = {
					x: 0, // 中心位置 x
					y: 0, // 中心位置 y
					z: 0, // 中心位置 z
					rX: 0, // 绕 X 轴旋转角度(弧度)
					rY: 0, // 绕 Y 轴旋转角度(弧度)
					s: 400, // 边长一半
					isTransform: false, // 是否变形, 是 则需配置长宽高sx,sy,sz, 否 则为正方体, 边长由 s 决定
					originOffset: [0,0,0], // 变换中心偏移
					sx: 400,
					sy: 400,
					sz: 400,
					lineWidth: 28,
					faceColor: [170,100,75],
					sideColor: [255,255,255,1],
					anchorColor: [120,100,75],
					anchorSize: 80,
					drawAnchor: true,
					drawSide: true,
					drawFace: true
				}
				option && Object.assign(c,option)
				var anchors = []
				var sides = []
				var faces = []
				if(!c.isTransform){
					c.sx = c.s
					c.sy = c.s
					c.sz = c.s
				}
				
				var pA = [c.sx,-c.sy,c.sz]
				var pB = [-c.sx,-c.sy,c.sz]
				var pC = [-c.sx,c.sy,c.sz]
				var pD = [c.sx,c.sy,c.sz]
				var pE = [c.sx,-c.sy,-c.sz]
				var pF = [-c.sx,-c.sy,-c.sz]
				var pG = [-c.sx,c.sy,-c.sz]
				var pH = [c.sx,c.sy,-c.sz]
				var anchors = [pA,pB,pC,pD,pE,pF,pG,pH]
				// 生成顶点坐标集
				c.drawAnchor && anchors.forEach(p=>{
					p[0] = p[0] + c.sx * c.originOffset[0]
					p[1] = p[1] + c.sy * c.originOffset[1]
					p[2] = p[2] + c.sz * c.originOffset[2]
					var res = this.rotateY(...this.rotateX(...p,c.rX),c.rY)
					p[0] = res[0] + c.x
					p[1] = res[1] + c.y
					p[2] = res[2] + c.z
				})
				// 生成棱集
				if(c.drawSide){
					sides.push([pA,pB]) // front
					sides.push([pB,pC])
					sides.push([pC,pD])
					sides.push([pD,pA])
					sides.push([pE,pF]) // back
					sides.push([pF,pG])
					sides.push([pG,pH])
					sides.push([pH,pE])
					sides.push([pA,pE]) // middle
					sides.push([pB,pF])
					sides.push([pC,pG])
					sides.push([pD,pH])
				}
				// 生成面集
				if(c.drawFace){
					faces.push([pA,pB,pC,pD])// front
					faces.push([pE,pH,pG,pF]) // back
					faces.push([pC,pG,pH,pD]) // down
					faces.push([pA,pE,pF,pB]) // up
					faces.push([pB,pF,pG,pC]) // left
					faces.push([pE,pA,pD,pH]) // right
				}
				return {
					anchors: {color: c.anchorColor,data: anchors,size: c.anchorSize},
					sides: {color: c.sideColor,data: sides,lineWidth: c.lineWidth},
					faces: {color: c.faceColor,data: faces},
					id: c.id
				}
			}
			var _construct = () => {
				cubicResourceList = []
				if(options){
					if(options.length !== undefined){
						options.forEach(option=>{
							cubicResourceList.push( _c(option)) // 用户配置的立方体
						})
					}else if(options.cubicOptionList){
						if(options.cubicOptionList.length !== undefined){
							options.cubicOptionList.forEach(option=>{
								cubicResourceList.push( _c(option)) // 用户配置的立方体
							})
						}else{
							cubicResourceList.push( _c()) // 默认配置立方体
						}
					}else{
						cubicResourceList.push( _c()) // 默认配置立方体
					}
				}else{
					cubicResourceList.push( _c()) // 默认配置立方体
				}
			}
			// 整合函数
			var _combine = () => {
				mixCRes ={
					anchors: [],
					sides: [],
					faces: []
				}
				cubicResourceList.forEach(cRes=>{ // 展开、整合方块点线面数据
					cRes.anchors.data.forEach(data => {
						mixCRes.anchors.push({color: cRes.anchors.color,data,size: cRes.anchors.size})
					})
					cRes.sides.data.forEach(data => {
						mixCRes.sides.push({color: cRes.sides.color,data,lineWidth: cRes.sides.lineWidth})
					})
					cRes.faces.data.forEach(data => {
						mixCRes.faces.push({color: cRes.faces.color,data})
					})
				})
			}
			_construct()
			_combine()
			// 返回单帧渲染回调
			return animations => {
				// 如果传入动画回调, 则利用该回调计算元数据
				// 并生成整合数据 替换 原有整合数据
				if(animations){
					animations(cubicResourceList)
				}
				_construct()
				_combine()
				this.drawFace && this.faceShader(mixCRes)
				this.drawSide && this.sideShader(mixCRes)
				this.drawAnchor && this.anchorShader(mixCRes)
			}
		}
	})
	w.Particle = Particle
}(document,window)